//
//  LLHTTPSessionManager.m
//  lrlzapp
//
//  Created by dl on 16/2/24.
//  Copyright © 2016年 lrlz. All rights reserved.
//

#import "LLHTTPSessionManager.h"
#import <CoreTelephony/CTTelephonyNetworkInfo.h>
#import <CoreTelephony/CTCarrier.h>
#import "LLHTTPUtil.h"

NSString *const OCTClientErrorDomain = @"OCTClientErrorDomain";
const NSInteger OCTClientErrorAuthenticationFailed = 666;
const NSInteger OCTClientErrorRequestForbidden = 674;
const NSInteger OCTClientErrorBadRequest = 670;
const NSInteger OCTClientErrorConnectionFailed = 668;
const NSInteger OCTClientErrorJSONParsingFailed = 669;

@interface LLHTTPSessionManager ()

@property(nonatomic, strong, readwrite) LLUser *user;
@property(nonatomic, copy, readwrite) NSString *token;

@end

@implementation LLHTTPSessionManager

+ (LLHTTPSessionManager *)shareManager {
    static LLHTTPSessionManager *shareManager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
        [config setHTTPAdditionalHeaders:@{@"User-Agent" : @"LRLZ IOS APP"}];
        [config setURLCache:[[NSURLCache alloc] initWithMemoryCapacity:10 * 1024 diskCapacity:50 * 1024 diskPath:nil]];
        shareManager = [[LLHTTPSessionManager alloc] initWithSessionConfiguration:config];
        shareManager.responseSerializer.acceptableContentTypes = HTTP_ACCEPTABLE_CONTENT_TYPES;
        shareManager.requestSerializer.timeoutInterval = INT_NETWORK_TIMEOUT;
        [shareManager.reachabilityManager startMonitoring];
    });
    return shareManager;
}

+ (dispatch_queue_t)globalSettingsQueue {
    static dispatch_queue_t settingsQueue;
    static dispatch_once_t pred;

    dispatch_once(&pred, ^{
        settingsQueue = dispatch_queue_create("com.lrlz.lrlzapp.globalSettingsQueue", DISPATCH_QUEUE_CONCURRENT);
    });

    return settingsQueue;
}

+ (NSError *)userRequiredError {
    NSDictionary *userInfo = @{
            NSLocalizedDescriptionKey : @"Username Required",
            NSLocalizedFailureReasonErrorKey : @"No username was provided for getting user information.",
    };

    return [NSError errorWithDomain:OCTClientErrorDomain code:OCTClientErrorAuthenticationFailed userInfo:userInfo];
}

+ (NSError *)authenticationRequiredError {
    NSDictionary *userInfo = @{
            NSLocalizedDescriptionKey : @"Sign In Required",
            NSLocalizedFailureReasonErrorKey : @"You must sign in to access user information.",
    };

    return [NSError errorWithDomain:OCTClientErrorDomain code:OCTClientErrorAuthenticationFailed userInfo:userInfo];
}

- (BOOL)isAuthenticated {
    return self.token != nil;
}

- (RACSignal *)loginWithUser:(LLUser *)user {
    NSString *url = @"smsLogin.ss";
    NSDictionary *params = @{@"phone" : user.phone, @"checkCode" : user.checkCode};
    return [[self request:url params:params resultClass:[LLUser class]]
            doNext:^(LLUser *user) {
                self.user = user;
                self.token = user.token;
            }];
}

- (RACSignal *)request:(NSString *)url
           resultClass:(Class)resultClass {
    return [self request:url params:nil resultClass:resultClass];
}

- (RACSignal *)request:(NSString *)url
                params:(NSDictionary *)params
           resultClass:(Class)resultClass {
    return [self request:url params:params method:GET_TYPE resultClass:resultClass];
}

- (RACSignal *)request:(NSString *)url
                params:(id)params
                method:(HttpMethodType)method
           resultClass:(Class)resultClass {
    RACSignal *signal =
            [[[RACSignal createSignal:^RACDisposable *(id <RACSubscriber> subscriber) {
                __block NSURLSessionDataTask *task =
                        [self _dataTaskWithHTTPMethod:method
                                                  url:url
                                               params:params
                                           completion:^(id response, NSError *error) {
                                               if (!error) {
                                                   [subscriber sendNext:response];
                                                   [subscriber sendCompleted];
                                               } else {
                                                   [subscriber sendError:error];
                                               }
                                           }];
                [task resume];
                return [RACDisposable disposableWithBlock:^{
                    [task cancel];
                }];
            }] replayLazily]
                    setNameWithFormat:@"%@ -rac(%@): %@, params: %@", self.class, method == GET_TYPE ? @"GET" : @"POST", url, params];

    return [[signal map:^id(id response) {
        return [self parsedResponseOfClass:resultClass fromJSON:response];
    }] concat];
}

- (NSURLSessionDataTask *)_dataTaskWithHTTPMethod:(HttpMethodType)method
                                              url:(NSString *)url
                                           params:(id)params
                                       completion:(void (^)(id, NSError *))completion {
    NSError *serializationError = nil;
    NSMutableURLRequest *request = [self requestWithMethod:method url:url params:params error:&serializationError];
    if (serializationError) {
        if (completion) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
            dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
                completion(nil, serializationError);
            });
#pragma clang diagnostic pop
        }
        return nil;
    }

    return [self dataTaskWithRequest:request
                   completionHandler:^(NSURLResponse *__unused response, id responseObject, NSError *error) {
                       if (completion) {
                           if (!error) {
                               completion(responseObject, nil);
                           } else {
                               completion(responseObject, error);
                           }
                       }
                   }];
}

- (RACSignal *)parsedResponseOfClass:(Class)resultClass fromJSON:(id)responseObject {
    NSParameterAssert(resultClass == nil || [resultClass isSubclassOfClass:MTLModel.class]);

    return [RACSignal createSignal:^id(id <RACSubscriber> subscriber) {
        void (^parseJSONDictionary)(NSDictionary *) = ^(NSDictionary *JSONDictionary) {
            if (resultClass == nil) {
                [subscriber sendNext:JSONDictionary];
                return;
            }

            NSError *error = nil;
            LLModel *parsedObject = [MTLJSONAdapter modelOfClass:resultClass fromJSONDictionary:JSONDictionary error:&error];
            if (parsedObject == nil) {
                if (![error.domain isEqual:MTLJSONAdapterErrorDomain] || error.code != MTLJSONAdapterErrorNoClassFound) {
                    [subscriber sendError:error];
                }
                return;
            }

            NSAssert([parsedObject isKindOfClass:MTLModel.class], @"Parsed model object is not an OCTObject: %@", parsedObject);
            [subscriber sendNext:parsedObject];
        };

        if ([responseObject isKindOfClass:NSArray.class]) {
            for (NSDictionary *JSONDictionary in responseObject) {
                if (![JSONDictionary isKindOfClass:NSDictionary.class]) {
                    NSString *failureReason = [NSString stringWithFormat:@"Invalid JSON array element: %@", JSONDictionary];
                    [subscriber sendError:[self parsingErrorWithFailureReason:failureReason]];
                    return nil;
                }
                parseJSONDictionary(JSONDictionary);
            }

            [subscriber sendCompleted];
        } else if ([responseObject isKindOfClass:NSDictionary.class]) {
            parseJSONDictionary(responseObject);
            [subscriber sendCompleted];
        } else if (responseObject == nil) {
            [subscriber sendNext:nil];
            [subscriber sendCompleted];
        } else {
            NSString *failureReason = [NSString stringWithFormat:@"Response wasn't an array or dictionary (%@): %@", [responseObject class], responseObject];
            [subscriber sendError:[self parsingErrorWithFailureReason:failureReason]];
        }

        return nil;
    }];
}

- (NSError *)parsingErrorWithFailureReason:(NSString *)localizedFailureReason {
    NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
    userInfo[NSLocalizedDescriptionKey] = @"Could not parse the service response.";

    if (localizedFailureReason != nil) {
        userInfo[NSLocalizedFailureReasonErrorKey] = localizedFailureReason;
    }

    return [NSError errorWithDomain:OCTClientErrorDomain code:OCTClientErrorJSONParsingFailed userInfo:userInfo];
}

- (RACSignal *)request:(NSString *)url
                params:(id)params
              fileData:(NSData *)fileData
              fileName:(NSString *)fileName
           resultClass:(Class)resultClass {
    return nil;
}

- (NSMutableURLRequest *)requestWithMethod:(HttpMethodType)method
                                       url:(NSString *)url
                                    params:(NSDictionary *)params
                                     error:(NSError **)error {
    NSDictionary *copyParams = [LLHTTPUtil getFinalParams:params url:url];
    NSString *urlWithBaseUrl = [LLHTTPUtil concactBaseUrlWithUrl:url];
    NSMutableURLRequest *request = [self.requestSerializer
            requestWithMethod:method == GET_TYPE ? @"GET" : @"POST"
                    URLString:[NSURL URLWithString:urlWithBaseUrl relativeToURL:self.baseURL].absoluteString
                   parameters:copyParams
                        error:error];
    return request;
}

@end
